// This is the main DLL file.

#include "stdafx.h"

#include "ApngImage.h"

using namespace Apng;
using namespace System::Runtime::InteropServices;
using namespace System::Windows::Media;

ApngImage::ApngImage()
{
	Fps = 25;

	this->Loaded += gcnew RoutedEventHandler(this, &ApngImage::OnLoaded);
	this->Unloaded += gcnew RoutedEventHandler(this, &ApngImage::OnUnloaded);
}

void ApngImage::OnLoaded(Object^ source, RoutedEventArgs^ e)
{
	Open();
	
	_timer = gcnew DispatcherTimer();
	_timer->Tick += gcnew EventHandler(this, &ApngImage::Tick);
	_timer->Interval = TimeSpan::FromMilliseconds(1000 / Fps);
	_timer->Start();
}

void ApngImage::OnUnloaded(Object^ source, RoutedEventArgs^ e)
{
	Close();

	_timer->Stop();
	_timer = nullptr;
}

void ApngImage::Open()
{
	_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	_info = png_create_info_struct(_png);
	
	IntPtr pPath = Marshal::StringToHGlobalAnsi(Path);
	const char* str = static_cast<const char*>(pPath.ToPointer());

	_fileHandle = fopen(str, "rb");
	
	Marshal::FreeHGlobal(pPath);

    png_init_io(_png, _fileHandle);
 
	png_read_info(_png, _info);
	png_uint_32 frames = png_get_num_frames(_png, _info);
	png_uint_32 plays = png_get_num_plays(_png, _info);
}

void ApngImage::Close()
{
	//png_read_end(_png, _info);
	png_struct * png = _png;
	png_info * info = _info;

	png_destroy_info_struct(png, &info);

	png_destroy_read_struct(&png, &info, NULL);

	fclose(_fileHandle);
}

void ApngImage::Tick(Object^ source, EventArgs^ e)
{
	static int frames = 0;
	if (frames == png_get_num_frames(_png, _info))
	{
		if (png_get_num_plays(_png,_info) == 0) //Loop
		{
			Close();
			Open();
			frames = 0;
		}
		else
		{
			return;
		}
	}
	frames ++;

	png_read_frame_head(_png, _info);

	png_uint_32 next_frame_width, next_frame_height;
	png_uint_32 next_frame_x_offset = 0, next_frame_y_offset = 0;
	png_uint_16 next_frame_delay_num, next_frame_delay_den;
	png_byte next_frame_dispose_op, next_frame_blend_op;

	if(png_get_valid(_png, _info, PNG_INFO_fcTL))
	{
		png_get_next_frame_fcTL(_png, _info,
                                    &next_frame_width, &next_frame_height,
                                    &next_frame_x_offset, &next_frame_y_offset,
                                    &next_frame_delay_num, &next_frame_delay_den,
                                    &next_frame_dispose_op, &next_frame_blend_op);
    }
    else
    {
        next_frame_width = png_get_next_frame_width(_png, _info);
        next_frame_height = png_get_next_frame_height(_png, _info);
    }

	png_bytep * row_pointers = (png_bytep*)png_malloc(_png, sizeof(png_bytep) * next_frame_height);
	for (png_uint_32 y=0; y<next_frame_height; y++)
		row_pointers[y] = (png_byte*)png_malloc(_png, _info->rowbytes);
	png_read_image(_png, row_pointers);
	
	array<unsigned char>^ buffer = gcnew array<unsigned char>(next_frame_height*_info->rowbytes);
	for (png_uint_32 y=0; y<next_frame_height; y++)
	{
		for (png_uint_32 x=0; x<next_frame_width; x++)
		{
			png_byte b = row_pointers[y][x*4];
			row_pointers[y][x*4] = row_pointers[y][x*4 + 2];
			row_pointers[y][x*4 + 2] = b;
		}
		Marshal::Copy((IntPtr)row_pointers[y], buffer, y*_info->rowbytes, _info->rowbytes);
	}

	if (_bitmap == nullptr)
	{
		PixelFormat format = PixelFormats::Bgra32;
		_bitmap = gcnew WriteableBitmap(next_frame_width, next_frame_height, 96, 96, format, nullptr);
		Source = _bitmap;
	}

	_bitmap->Lock();

	_bitmap->WritePixels(Int32Rect(0, 0, next_frame_width, next_frame_height), buffer, _info->rowbytes, next_frame_x_offset, next_frame_y_offset);
	
	_bitmap->Unlock();

	for (png_uint_32 y=0; y<next_frame_height; y++)
		png_free(_png, row_pointers[y]);
	png_free(_png, row_pointers);

}